/*
 * Copyright (c) 2005-2006 Arch Rock Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the
 *   distribution.
 * - Neither the name of the Arch Rock Corporation nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * ARCHED ROCK OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE
 */

/**
 * @author Jonathan Hui <jhui@archrock.com>
 * @version $Revision: 1.4 $ $Date: 2006-12-12 18:23:13 $
 */
#include "crc.h"
//#define POWERDOWN

module Stm25pSpiP {

  provides interface Init;
  provides interface Resource as ClientResource;
  provides interface Stm25pSpi as Spi;

  uses interface Resource as SpiResource;
  uses interface GeneralIO as CSN;
  uses interface GeneralIO as Hold;
  uses interface GeneralIO as Write;
  uses interface GeneralIO as Power;
  uses interface SpiByte;
  uses interface SpiPacket;
  uses interface Leds;
}

implementation {

	enum 
  	{
    	CRC_BUF_SIZE = 16,
  	};

	typedef enum 
	{
    	S_READ = 0x3,
    	S_PAGE_PROGRAM = 0x2,
    	S_SECTOR_ERASE = 0xd8,
    	S_BULK_ERASE = 0xc7,
    	S_WRITE_ENABLE = 0x6,
    	S_POWER_ON = 0xab,
    	S_DEEP_SLEEP = 0xb9,
  	} stm25p_cmd_t;

  	norace uint8_t m_cmd[ 4 ];

  	norace bool m_is_writing = FALSE;
  	norace bool m_computing_crc = FALSE;
  	bool m_init = FALSE;

  	norace stm25p_addr_t m_addr;
  	norace uint8_t* m_buf;
  	norace stm25p_len_t m_len;
  	norace stm25p_addr_t m_cur_addr;
  	norace stm25p_len_t m_cur_len;
  	norace uint8_t m_crc_buf[ CRC_BUF_SIZE ];
  	norace uint16_t m_crc;

  	error_t newRequest( bool write, stm25p_len_t cmd_len );
  	void signalDone( error_t error );

  	uint8_t sendCmd( uint8_t cmd, uint8_t len ) 
  	{

    	uint8_t tmp = 0;
    	int i;

    	call CSN.clr();
    	for ( i = 0; i < len; i++ ) tmp = call SpiByte.write( cmd );
    	call CSN.set();

    	return tmp;
  	}

  	command error_t Init.init() 
  	{
  		call Power.makeOutput();
  		call CSN.makeOutput();
    	call Hold.makeOutput();
    	call Write.makeOutput();
  		
  		call Power.clr();
    	call CSN.set();
    	call Hold.set();
    	call Write.set();
    	
    	#ifdef POWERDOWN
    		call Spi.powerDown();
    	#else
    	
	    	if(call SpiResource.immediateRequest() == SUCCESS)
	    	{
	      		call Spi.powerDown();
	      		call SpiResource.release();
	    	} 
	    	else if(call SpiResource.request()==SUCCESS)
	    	{
	     	 	m_init=TRUE;//otherwise we can't put the chip to deep sleep
	     	}
     	#endif
    	return SUCCESS;
  	}

  	async command error_t ClientResource.request() 
  	{
    	return call SpiResource.request();
  	}

  	async command error_t ClientResource.immediateRequest() 
  	{
  		return call SpiResource.immediateRequest();
  	}
  
  	async command error_t ClientResource.release() 
  	{
    	return call SpiResource.release();
  	}

  	async command bool ClientResource.isOwner() 
  	{
    	return call SpiResource.isOwner();
  	}

  	stm25p_len_t calcReadLen() 
  	{
    	return ( m_cur_len < CRC_BUF_SIZE ) ? m_cur_len : CRC_BUF_SIZE;
  	}  

  	async command error_t Spi.powerDown() 
  	{
 		#ifdef POWERDOWN
	 		call Power.set();
			call CSN.clr();
			call Hold.clr();
			call Write.clr();
 		#else 	
	    	sendCmd( S_DEEP_SLEEP, 1 );
    	#endif
    	
    	return SUCCESS;
  	}

  	async command error_t Spi.powerUp() 
  	{
  		#ifdef POWERDOWN
  		    call CSN.set();
  			call Power.clr();
			call Hold.set();
			call Write.set();
  		#else
	  		sendCmd( S_POWER_ON, 5 );
  		#endif
  		
  		return SUCCESS;
  	}

  	async command error_t Spi.read( stm25p_addr_t addr, uint8_t* buf, stm25p_len_t len ) 
  	{
    	m_cmd[ 0 ] = S_READ;
    	m_addr = addr;
    	m_buf = buf;
    	m_len = len;
    	
    	return newRequest( FALSE, 4 );
  	}

  	async command error_t Spi.computeCrc( uint16_t crc, stm25p_addr_t addr, stm25p_len_t len ) 
  	{
    	m_computing_crc = TRUE;
    	m_crc = crc;
    	m_addr = m_cur_addr = addr;
    	m_len = m_cur_len = len;
    	
    	return call Spi.read( addr, m_crc_buf, calcReadLen() );
  	}
  
  	async command error_t Spi.pageProgram( stm25p_addr_t addr, uint8_t* buf, stm25p_len_t len ) 
  	{
    	m_cmd[ 0 ] = S_PAGE_PROGRAM;
    	m_addr = addr;
    	m_buf = buf;
    	m_len = len;
    	
    	return newRequest( TRUE, 4 );
  	}

  	async command error_t Spi.sectorErase( uint8_t sector ) 
  	{
    	m_cmd[ 0 ] = S_SECTOR_ERASE;
    	m_addr = (stm25p_addr_t)sector << STM25P_SECTOR_SIZE_LOG2;
    	
    	return newRequest( TRUE, 4 );
  	}

  	async command error_t Spi.bulkErase() 
  	{
    	m_cmd[ 0 ] = S_BULK_ERASE;
    	return newRequest( TRUE, 1 );
  	}

  	error_t newRequest( bool write, stm25p_len_t cmd_len ) 
  	{
    	m_cmd[ 1 ] = m_addr >> 16;
    	m_cmd[ 2 ] = m_addr >> 8;
    	m_cmd[ 3 ] = m_addr;
    	if ( write ) sendCmd( S_WRITE_ENABLE, 1 );
    	call CSN.clr();
    	call SpiPacket.send( m_cmd, NULL, cmd_len );
    	
    	return SUCCESS;
  	}

  	void releaseAndRequest() 
  	{
    	call SpiResource.release();
    	call SpiResource.request();
  	}

  	async event void SpiPacket.sendDone( uint8_t* tx_buf, uint8_t* rx_buf, uint16_t len, error_t error ) 
  	{

    	int i;

    	switch( m_cmd[ 0 ] ) 
    	{
    		case S_READ:
      			if ( tx_buf == m_cmd ) 
      			{
					call SpiPacket.send( NULL, m_buf, m_len );
					break;
      			}
      			else if ( m_computing_crc ) 
      			{
					for ( i = 0; i < len; i++ )
	 				m_crc = crcByte( m_crc, m_crc_buf[ i ] );
					m_cur_addr += len;
					m_cur_len -= len;
					if ( m_cur_len ) 
					{
	  					call SpiPacket.send( NULL, m_crc_buf, calcReadLen() );
	  					break;
					}
      			}
      			call CSN.set();
      			signalDone( SUCCESS );
      			
      			break;

    		case S_PAGE_PROGRAM:
      			if ( tx_buf == m_cmd ) 
      			{
					call SpiPacket.send( m_buf, NULL, m_len );
					break;
      			}
      			// fall through
      
    		case S_SECTOR_ERASE: case S_BULK_ERASE:
      			call CSN.set();
      			m_is_writing = TRUE;
      			releaseAndRequest();
      			break;

    		default:
      		break;
    	}

  	}

  	event void SpiResource.granted() 
  	{
  		if (m_init)
  		{
      		m_init=FALSE;
      		call Spi.powerDown();
      		call SpiResource.release();
    	}
    	else if ( !m_is_writing ) signal ClientResource.granted();
    	else if ( sendCmd( 0x5, 2 ) & 0x1 ) releaseAndRequest();
    	else signalDone( SUCCESS );
  	}

  	void signalDone( error_t error ) 
  	{
    	m_is_writing = FALSE;
    	switch( m_cmd[ 0 ] ) 
    	{
    		case S_READ:
      			if ( m_computing_crc ) 
      			{
					m_computing_crc = FALSE;
					signal Spi.computeCrcDone( m_crc, m_addr, m_len, error );
      			}
      			else 
      			{
					signal Spi.readDone( m_addr, m_buf, m_len, error );
      			}
      			break;
    		case S_PAGE_PROGRAM:
      			signal Spi.pageProgramDone( m_addr, m_buf, m_len, error );
      			break;
    		case S_SECTOR_ERASE:
      			signal Spi.sectorEraseDone( m_addr >> STM25P_SECTOR_SIZE_LOG2, error );
      			break;
    		case S_BULK_ERASE:
      			signal Spi.bulkEraseDone( error );
      		break;
    	}
  	}
  	
  	default event void ClientResource.granted() {};
  	
  	default async event void Spi.readDone( stm25p_addr_t addr, uint8_t* buf, stm25p_len_t len, error_t error ) {};
  	default async event void Spi.computeCrcDone( uint16_t crc, stm25p_addr_t addr, stm25p_len_t len, error_t error ) {};
  	default async event void Spi.pageProgramDone( stm25p_addr_t addr, uint8_t* buf, stm25p_len_t len, error_t error ) {};
  	default async event void Spi.sectorEraseDone( uint8_t sector, error_t error ) {};
  	default async event void Spi.bulkEraseDone( error_t error ) {};
}
